import type {
  ClockViewModel,
  DataSourceCollection,
  GeocoderService,
  Globe,
  ImageryProvider,
  MapMode2D,
  MapProjection,
  ProviderViewModel,
  Scene,
  SceneMode,
  ShadowMode,
  SkyAtmosphere,
  SkyBox,
  TerrainProvider
} from "cesium"
import type {
  GCS,
  ICameraInfo
} from "../CustomCesium"
import {
  CallbackProperty,
  Cartesian2,
  Cartesian3,
  Cartographic,
  Math as CesiumMath,
  Color,
  createGuid,
  CustomDataSource,
  defined,
  Entity,
  ImageMaterialProperty,
  Ion,
  Ray,
  Rectangle,
  sampleTerrain,
  ScreenSpaceEventType,
  SingleTileImageryProvider,
  UrlTemplateImageryProvider,
  Viewer,
  WebMapTileServiceImageryProvider
} from "cesium"
import SuperGif from "libgif"
/*
 * @Descripttion:
 * @version: 1.0.0
 * @Date: 2020-11-11 11:30:13
 * @LastEditTime: 2022-06-15 10:48:18
 */
import GlobeBG from "../Assets/globeBG.jpg"
import SelectionBox from "../Assets/select.png"
import SkyBoxNX from "../Assets/SkyBox/skybox_nx.jpg"
import SkyBoxNY from "../Assets/SkyBox/skybox_ny.jpg"
import SkyBoxNZ from "../Assets/SkyBox/skybox_nz.jpg"
import SkyBoxPX from "../Assets/SkyBox/skybox_px.jpg"
import SkyBoxPY from "../Assets/SkyBox/skybox_py.jpg"
import SkyBoxPZ from "../Assets/SkyBox/skybox_pz.jpg"
import {
  AnimationManager,
  CameraRoam,
  LayerManager,
  MathUtils,
  MouseHandler,
  ParticleAnimation,
  SceneListener,
  SceneListenerType,
  SystemTime,
  WindSystem
} from "../CustomCesium"
import DrawingManager from "./DrawingManager"
import "cesium/Build/Cesium/Widgets/widgets.css"

/**
 * Initialization options for the Viewer constructor
 */
interface ICesiumViewerOptions {
  animation?: boolean
  baseLayerPicker?: boolean
  fullscreenButton?: boolean
  vrButton?: boolean
  geocoder?: boolean | GeocoderService[]
  homeButton?: boolean
  infoBox?: boolean
  sceneModePicker?: boolean
  selectionIndicator?: boolean
  timeline?: boolean
  navigationHelpButton?: boolean
  navigationInstructionsInitiallyVisible?: boolean
  scene3DOnly?: boolean
  shouldAnimate?: boolean
  clockViewModel?: ClockViewModel
  selectedImageryProviderViewModel?: ProviderViewModel
  imageryProviderViewModels?: ProviderViewModel[]
  selectedTerrainProviderViewModel?: ProviderViewModel
  terrainProviderViewModels?: ProviderViewModel[]
  imageryProvider?: ImageryProvider
  terrainProvider?: TerrainProvider
  skyBox?: SkyBox | false
  skyAtmosphere?: SkyAtmosphere | false
  fullscreenElement?: Element | string
  useDefaultRenderLoop?: boolean
  targetFrameRate?: number
  showRenderLoopErrors?: boolean
  useBrowserRecommendedResolution?: boolean
  automaticallyTrackDataSourceClocks?: boolean
  contextOptions?: any
  sceneMode?: SceneMode
  mapProjection?: MapProjection
  globe?: Globe | false
  orderIndependentTranslucency?: boolean
  creditContainer?: Element | string
  creditViewport?: Element | string
  dataSources?: DataSourceCollection
  terrainExaggeration?: number
  shadows?: boolean
  terrainShadows?: ShadowMode
  mapMode2D?: MapMode2D
  projectionPicker?: boolean
  requestRenderMode?: boolean
  maximumRenderTimeChange?: number
}

/**
 * 圆形画布
 */
interface ICanvasCircle {
  color: Color
  blur?: Color
  lineWidth?: number
}

/**
 * 圆形材质带坐标
 */
export interface ICircleWithPosition extends ICanvasCircle {
  gcs: GCS
}

/**
 * 初始配置
 * @type {{animation: boolean, baseLayerPicker: boolean, fullscreenButton: boolean, geocoder: boolean, homeButton: boolean, infoBox: boolean, sceneModePicker: boolean, selectionIndicator: boolean, timeline: boolean, navigationHelpButton: boolean, navigationInstructionsInitiallyVisible: boolean, automaticallyTrackDataSourceClocks: boolean, imageryProvider}}
 */
let cesiumViewerOptions: ICesiumViewerOptions = {
  // skyBox: new SkyBox({
  //   sources: {
  //     positiveX: SkyBoxPX,
  //     negativeX: SkyBoxNX,
  //     positiveY: SkyBoxPY,
  //     negativeY: SkyBoxNY,
  //     positiveZ: SkyBoxPZ,
  //     negativeZ: SkyBoxNZ,
  //   },
  //   show: true,
  // }),
  // skyAtmosphere: new SkyAtmosphere(),
  timeline: true, // 是否显示时间线控件
  animation: true, // 是否显示动画控件
  shouldAnimate: true, // 是否动画
  baseLayerPicker: false,
  fullscreenButton: false,
  geocoder: false,
  homeButton: false,
  infoBox: false,
  sceneModePicker: false,
  //   selectionIndicator: false,
  navigationHelpButton: false,
  navigationInstructionsInitiallyVisible: false,
  automaticallyTrackDataSourceClocks: false,
  // 如果为true，如果出现渲染循环错误，此小部件将自动向用户显示包含错误的HTML面板。
  //   showRenderLoopErrors: false,
  // terrainProvider: Cesium.createWorldTerrain(),
  imageryProvider: new SingleTileImageryProvider({
    url: GlobeBG,
    rectangle: Rectangle.MAX_VALUE
  })
}

// token
Ion.defaultAccessToken
  = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJmNTg2OTNhYi1hM2JmLTQyYTItOWE1NS0wMzNjMzAyZDI3NGYiLCJpZCI6MjU5MTAsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1ODY4MzI4NDV9.2DP9UQowHfxa656C1UZT7vVvMk39xJSPTL83-Ce-Ypg"
// Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJmMTYxYjE3Zi0yM2ZjLTQzOTUtOTUyZS0wNGRlYTI0NzZkNWEiLCJpZCI6MjU5LCJpYXQiOjE2MjI1Nzc1NzF9.wBdlWsqCoHM9tpplqxAPCdQWsERtxJc65IFZRf4g0z4';

/**
 * @descripttion: 地球场景类
 */
export default class CustomViewer {
  // 视图容器
  private container: string | Element
  // 时间管理器
  timeManager: SystemTime | undefined
  // 图层记录管理器
  layerManager: LayerManager | undefined
  // 风力天气管理器
  windManager: WindSystem | undefined
  // 粒子管理器
  particleManager: ParticleAnimation | undefined
  // 漫游工具
  roamManager: CameraRoam | undefined
  // 监听事件管理器
  mouseHandlerManager: MouseHandler | undefined
  // 场景监听管理器
  sceneListener: SceneListener | undefined
  // 动画管理器
  animationManager: AnimationManager | undefined
  // 绘画管理器
  drawingManager: DrawingManager | null
  // 视图
  viewer: Viewer

  /**
   * @descripttion: 初始化参数
   * @param {Document} container 容器
   * @param {object} layerLevel 图层级别
   * @param {string} hostUrl 用户地址
   * @param {object} viewerOptions 视图参数
   * @return {type} 球
   */
  constructor(
    container: string | Element,
    viewerOptions: ICesiumViewerOptions | undefined = undefined
  ) {
    /** 初始化参数 */
    this.container = container
    /** 工具类 */
    this.timeManager = undefined // 时间管理器
    this.layerManager = undefined // 图层记录管理器
    this.windManager = undefined // 风力天气管理器
    this.particleManager = undefined // 粒子管理器
    this.roamManager = undefined // 漫游工具
    this.mouseHandlerManager = undefined // 监听事件管理器
    this.animationManager = undefined // 动画管理器
    this.drawingManager = null // 绘图管理器
    /** 初始化类 */
    this.viewer = this.initViewer(viewerOptions) // cesium视图
  }

  /**
   * @descripttion: cesium视图初始化
   * @param {ICesiumViewerOptions} options
   * @return {*}
   */
  private initViewer(options: ICesiumViewerOptions | undefined) {
    options && (cesiumViewerOptions = { ...cesiumViewerOptions, ...options })

    const viewer = (this.viewer = new Viewer(
      this.container,
      cesiumViewerOptions
    ))

    viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(
      ScreenSpaceEventType.LEFT_DOUBLE_CLICK
    )
    viewer.camera.positionCartographic.height = 200000000

    // 设置相机视野框改变幅度触发相机改变事件
    viewer.scene.camera.percentageChanged = 0.1

    // 开启大气层
    // viewer.scene.skyAtmosphere = new SkyAtmosphere();

    // 太阳
    // viewer.scene.sun = new Sun();

    // 天空
    // viewer.scene.skyBox = new SkyBox({
    // sources: {
    //   positiveX: './assets/cesiumImg/SkyBox/skybox_px.jpg',
    //   negativeX: './assets/cesiumImg/SkyBox/skybox_nx.jpg',
    //   positiveY: './assets/cesiumImg/SkyBox/skybox_py.jpg',
    //   negativeY: './assets/cesiumImg/SkyBox/skybox_ny.jpg',
    //   positiveZ: './assets/cesiumImg/SkyBox/skybox_pz.jpg',
    //   negativeZ: './assets/cesiumImg/SkyBox/skybox_nz.jpg',
    // },
    //   show: true,
    // });

    // viewer.scene.debugShowFramesPerSecond = true;
    // 深度开启或关闭
    // viewer.scene.globe.depthTestAgainstTerrain = true;

    // 近地光照开启
    // viewer.scene.globe.enableLighting = true;

    // 开启光照遮挡
    // viewer.scene.light.intensity = 2; //2透视光

    // viewer.scene.fxaa = true;

    viewer.scene.postProcessStages.fxaa.enabled = false // 去锯齿 是文字清晰

    // viewer.scene._view.oit.useTranslucentDepth = false;

    // if (FeatureDetection.supportsImageRenderingPixelated()) {//判断是否支持图像渲染像素化处理
    viewer.resolutionScale = window.devicePixelRatio
    // }

    // viewer.scene.screenSpaceCameraController.enableTilt = false; //禁用鼠标右键

    // dem调试模式
    // viewer.extend(Cesium.viewerCesiumInspectorMixin);

    // 初始化时间管理器
    const timeManager = this.initTimeManager(viewer)
    // 切换到本地时间
    timeManager?.initLocaleTime()
    // 隐藏时间控件
    timeManager?.hiddenTimeWidget()

    // 初始化图层管理器
    this.initLayerManager(viewer)

    // 初始化场景监听管理器
    this.initSceneListener(viewer.scene)

    // 初始化粒子管理器
    this.initParticleManager(viewer)

    // 初始化风场管理器
    this.initWindSystem()

    // 初始化漫游管理器
    this.initRoamManager(viewer)

    // 初始化鼠标管理器
    const mouseHandler = this.initMouseHandlerManager(viewer)

    // 初始化动画管理器
    this.initAnimationManager(
      viewer,
      this.roamManager!,
      this.particleManager!,
      this.windManager!
    )
    // 显示刷新率和帧率
    // viewer.scene.debugShowFramesPerSecond = true;

    // 初始化绘图管理器
    this.initDrawingManager(viewer, mouseHandler!)

    return viewer
  }

  /**
   * @descripttion: 加载基础图层
   * @return {void}
   */
  loadBaseLayers(hostUrl = "172.16.0") {
    /** ******imageLayer */
    let globeProvider:
      | WebMapTileServiceImageryProvider
      | UrlTemplateImageryProvider,
      labelProvider: UrlTemplateImageryProvider
    if (
      hostUrl.includes("172.16.0")
      || hostUrl.includes("localhost")
      || hostUrl.includes("192.168.0")
    ) {
      // 添加天地图
      globeProvider = new WebMapTileServiceImageryProvider({
        url: "http://{s}.tianditu.com/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=913250fb249e4264eb86f83b65cba4a9",
        layer: "tdtBasicLayer",
        style: "default",
        minimumLevel: 0,
        maximumLevel: 15,
        format: "image/jpeg",
        tileMatrixSetID: "GoogleMapsCompatible",
        subdomains: ["t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"]
      })
      globeProvider.readyPromise.then((status) => {
        if (status) {
          this.layerManager?.addLayer("baseImageLayer", globeProvider)
        }
      })

      // Url
      // globeProvider = new Cesium.UrlTemplateImageryProvider({
      //     url: 'https://vms.cnpc.com.cn:7902/arcgis/rest/services/Map/china/MapServer/tile/{z}/{x}/{y}?token=1FMKCWARLQ-LjM0SMmCOZcqsQzZn2GmnYDS5JaTrM3SkE6q4-m6iFQZwjm8eFxTHoZT9P_uMzM72y7qjufQ7eQ..',
      // });
      // ArcMap
      // globeProvider = new Cesium.ArcGisMapServerImageryProvider({
      //     // // online
      //     // url: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer",
      //     // // http
      //     // url: 'http://vmsgis.cnpc.com.cn:9001/arcgis/rest/services/Map/chinaimage/MapServer',
      //     // token: "VPdJZnAZCyPzq-cU2ShEUirPD0nKLbAP7CvPA2hlfmdCh5pXF5wcbZmTzQf4LqRG",
      //     // https
      //     // url: 'https://vms.cnpc.com.cn:7902/arcgis/rest/services/Map/chinaimage/MapServer',
      //     // token: "SFJnaLGvKf7s_d3em1aPLqyK5AJH-tatOTXvdQ20Q_ckW5JSipyrNP1nm9ITvWiS",
      //     // // label
      //     url: 'https://vms.cnpc.com.cn:7902/arcgis/rest/services/Map/china/MapServer/tile',
      //     token: '1FMKCWARLQ-LjM0SMmCOZcqsQzZn2GmnYDS5JaTrM3SkE6q4-m6iFQZwjm8eFxTHoZT9P_uMzM72y7qjufQ7eQ..'
      // });
      // GoogleMap
      // globeProvider = new Cesium.UrlTemplateImageryProvider({
      //     url: "http://mt1.google.cn/vt/lyrs=s&hl=zh-CN&x={x}&y={y}&z={z}&s=Gali",
      //     tilingScheme: new Cesium.WebMercatorTilingScheme(),
      //     // minimumLevel: this.layerLevel.minimumLevel,
      //     // maximumLevel: this.layerLevel.maximumLevel,
      // });
    } else {
      // 根据相机高度加载客户地图服务
      // A4服务
      globeProvider = new UrlTemplateImageryProvider({
        url: "http://a4.petrochina/A4Service/TileService.ashx?Type=TDTIMG&c={x}&r={y}&l={z}&token=5a2ccb37-aa54-4653-942b-222cb9ccf363",
        minimumLevel: 0,
        maximumLevel: 18
      })

      globeProvider.readyPromise.then((status) => {
        if (status) {
          this.layerManager?.addLayer("baseImageLayer", globeProvider)
        }
      })

      // A4矢量注记
      labelProvider = new UrlTemplateImageryProvider({
        url: "http://a4.petrochina/A4Service/TileService.ashx?Type=TDTCVA&c={x}&r={y}&l={z}&token=5a2ccb37-aa54-4653-942b-222cb9ccf363",
        minimumLevel: 0,
        maximumLevel: 18
      })

      labelProvider.readyPromise.then((status) => {
        if (status) {
          this.layerManager?.addLayer("baseLabelLayer", labelProvider)
        }
      })
    }

    // //添加底图
    // let imageryProvider = new Cesium.WebMapTileServiceImageryProvider({
    //     url: 'http://{s}.tianditu.com/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=913250fb249e4264eb86f83b65cba4a9',
    //     layer: 'tdtBasicLayer',
    //     style: 'default',
    //     minimumLevel: 0,
    //     maximumLevel: 12,
    //     format: 'image/jpeg',
    //     tileMatrixSetID: 'GoogleMapsCompatible',
    //     show: true,
    //     subdomains: ['t0', 't1', 't2', 't3', 't4', 't5', 't6'],
    // });
    // globeProvider = layers.addImageryProvider(imageryProvider);
  }

  /**
   * @descripttion: 初始化视图框
   * @param {callback} callback 返回值
   * @return {void}
   */
  initFullExtend(
    position: Cartesian3 = Cartesian3.fromDegrees(
      107.7004,
      35.5588,
      17894750.737236
    ),
    callback?: (res: boolean) => void
  ) {
    // 17894750
    this.viewer.camera.flyTo({
      destination: position,
      duration: 5,
      easingFunction: time => time * (2 - time),
      complete: () => {
        callback && callback(true)
      }
    })
  }

  /**
   * --------------------------------------------------------
   * ------------------------工具类--------------------------
   * --------------------------------------------------------
   */

  /**
   * @descripttion: 初始化粒子系统
   * @param {Viewer} viewer
   * @return {*}
   */
  private initParticleManager(viewer: Viewer) {
    if (!this.particleManager)
      this.particleManager = new ParticleAnimation(viewer)
    return this.particleManager
  }

  /**
   * @descripttion: 初始化风力天气系统
   * @return {object} 管理器
   */
  private initWindSystem() {
    let manager
    if (!this.windManager) manager = this.windManager = new WindSystem()
    return manager
  }

  /**
   * @descripttion: 初始化图层记录管理器
   * @return {object} 图层记录管理器
   */
  private initLayerManager(viewer: Viewer) {
    let manager
    if (!this.layerManager)
      manager = this.layerManager = new LayerManager(viewer)
    return manager
  }

  /**
   * @descripttion: 初始化时间管理器
   * @param {*} viewer cesium视图管理器
   * @return {object} 管理器
   */
  private initTimeManager(viewer: Viewer) {
    let manager = this.timeManager
    if (!this.timeManager) manager = this.timeManager = new SystemTime(viewer)
    return manager
  }

  /**
   * @descripttion: 初始化漫游管理器
   * @return {object} 管理器
   */
  private initRoamManager(viewer: Viewer) {
    let manager = this.roamManager
    if (!this.roamManager) manager = this.roamManager = new CameraRoam(viewer)
    return manager
  }

  /**
   * @descripttion: 初始化监听事件管理器
   * @param {*} viewer cesium视图管理器
   * @return {Manager} 管理器
   */
  private initMouseHandlerManager(viewer: Viewer) {
    let manager = this.mouseHandlerManager
    if (!this.mouseHandlerManager)
      manager = this.mouseHandlerManager = new MouseHandler(viewer)
    return manager
  }

  /**
   * @descripttion: 初始化场景监听管理器
   * @return {object} 管理器
   */
  private initSceneListener(scene: Scene) {
    let manager = this.sceneListener
    if (!this.sceneListener)
      manager = this.sceneListener = new SceneListener(scene)
    return manager
  }

  /**
   * @descripttion: 初始化动画管理器
   * @param {*}
   * @return {*}
   */
  private initAnimationManager(
    viewer: Viewer,
    roamManager: CameraRoam,
    particleManager: ParticleAnimation,
    windManager: WindSystem
  ) {
    let manager = this.animationManager
    if (!this.animationManager) {
      manager = this.animationManager = new AnimationManager(
        viewer,
        roamManager,
        particleManager,
        windManager
      )
    }
    return manager
  }

  /**
   * @descripttion: 初始化绘画管理器
   * @param {*}
   * @return {*}
   */
  private initDrawingManager(
    viewer: Viewer,
    mouseHandlerManager: MouseHandler
  ) {
    let manager = this.drawingManager
    if (!this.drawingManager) {
      manager = this.drawingManager = new DrawingManager(
        viewer,
        mouseHandlerManager
      )
    }
    return manager
  }

  /**
   * @descripttion: 相机改变监听事件
   * @param {Function} callback 回调函数
   * @return {null} null
   */
  listenCameraChange = (callback: (res: ICameraInfo) => void) => {
    this.viewer.scene.camera.changed.addEventListener(() => {
      callback(this.getCenterPosition())
    })
  }

  /**
   * @descripttion: 获取当前层级
   * @param {Viewer} viewer
   * @return {*}
   */
  getCurrentLevel(viewer: Viewer) {
    const height = Math.ceil(viewer.camera.positionCartographic.height)
    const A = 40487.57
    const B = 0.00007096758
    const C = 91610.74
    const D = -40467.74
    return Math.round(D + (A - D) / (1 + (height / C) ** B))
  }

  /**
   * @descripttion: 根据层级获取地形高程
   * @param {Cartesian3} car3 笛卡尔坐标
   * @param {number} level 层级
   * @return {Promise} 回调
   */
  getTerrainHeightByLevel(
    viewer: Viewer,
    longitude: number,
    latitude: number,
    level: number
  ) {
    const cartographic = Cartographic.fromDegrees(longitude, latitude)
    const cartographics: Cartographic[] = [cartographic]
    const promise = sampleTerrain(
      viewer.scene.terrainProvider,
      level,
      cartographics
    )
    return promise
  }

  /**
   * @descripttion: 根据经纬度获取实时高程
   * @param {Viewer} viewer
   * @param {number} longitude
   * @param {number} latitude
   * @return {*}
   */
  getRealTimeTerrainHeight(
    viewer: Viewer,
    longitude: number,
    latitude: number
  ) {
    const cartographic = Cartographic.fromDegrees(longitude, latitude)
    const height = viewer.scene.globe.getHeight(cartographic)
    return height
  }

  /**
   * 获取当前视野中心位置
   * @return {object} 位置信息
   */
  getCenterPosition() {
    // 获取相机地理坐标
    const cartographic = this.viewer.camera.positionCartographic.clone()
    // 相机参数
    const roll = this.viewer.camera.roll
    const heading = this.viewer.camera.heading
    const pitch = this.viewer.camera.pitch
    const lon = cartographic.longitude
    const lat = cartographic.latitude
    const height = cartographic.height
    // 获取该点地表高程
    const altitude = this.viewer.scene.globe.getHeight(cartographic) || 0
    return {
      lon: CesiumMath.toDegrees(lon),
      lat: CesiumMath.toDegrees(lat),
      altitude: altitude < 0 ? 0 : altitude,
      roll: CesiumMath.toDegrees(roll),
      heading: CesiumMath.toDegrees(heading),
      pitch: CesiumMath.toDegrees(pitch),
      height
    }
  }

  /**
   * @descripttion: 获取相机视点位置
   * @param {*} viewer cesium视图
   * @return {Cartesian3} 视点
   */
  getViewPosition(viewer: Viewer) {
    // 相机方向
    const direction = { ...viewer.camera.direction } as Cartesian3
    // 相机中心点
    const center = { ...viewer.camera.position } as Cartesian3
    // 以相机中心点、相机方向创建射线
    const cameraRay = new Ray(center, direction)
    // 射线与地球场景交点
    const result = viewer.scene.globe.pick(cameraRay, viewer.scene)
    return result
  }

  /**
   * --------------------------------------------------------
   * --------------------------渲染-------------------------
   * -------------------------------------------------------
   */

  /**
   * @descripttion: 绘制圆形(带阴影)图片
   * @param {number} radius 半径
   * @param {Color} color 颜色
   * @param {boolean} reverse 是否反向
   * @return {*}
   */
  drawCircleImage(
    radius: number,
    color: Color,
    blur?: Color,
    lineWidth?: number,
    reverse = false
  ) {
    const width = radius * 2
    const colorCss = color.toCssColorString()
    const blurCss = blur
      ? blur.toCssColorString()
      : lineWidth
        ? Color.TRANSPARENT.toCssColorString()
        : color.withAlpha(0.2).toCssColorString()

    // 创建画布
    const canvas = document.createElement("canvas")
    canvas.width = width
    canvas.height = width

    // 填充画布内容
    const ctx = canvas.getContext("2d")!

    // 开始画圆
    ctx.beginPath()
    ctx.arc(radius, radius, radius, 0, 360 * Math.PI)
    ctx.closePath()

    // 创建渐变
    const grd = ctx.createRadialGradient(
      radius,
      radius,
      lineWidth ? radius - lineWidth : radius / 2,
      radius,
      radius,
      radius
    )
    if (reverse) {
      grd.addColorStop(0, colorCss)
      grd.addColorStop(1, blurCss)
    } else {
      grd.addColorStop(0, blurCss)
      grd.addColorStop(1, colorCss)
    }

    // 填充样式
    ctx.fillStyle = grd
    ctx.fill()

    return canvas
  }

  /**
   * @descripttion: 渲染帧数组
   * @param {string} url gif地址
   * @param {string} frames 帧数组
   * @param {number} interval 时间周期(毫秒)
   * @return {*}
   */
  drawImagefromGIF(url: string, interval = 2000) {
    let index = 0
    const frames: string[] = [];

    (function createFramesFromGIF(imageArr: string[]) {
      // 创建图片元素
      const img = document.createElement("img")
      img.src = url
      // gif库需要img标签配置下面两个属性
      img.setAttribute("rel:animated_src", url)
      img.setAttribute("rel:auto_play", "0")
      document.body.appendChild(img)
      // 新建gif实例
      const rub = new SuperGif({ gif: img })
      return new Promise((resolve) => {
        rub.load(() => {
          for (let i = 1; i <= rub.get_length(); i++) {
            // 遍历gif实例的每一帧
            rub.move_to(i)
            imageArr.push(rub.get_canvas().toDataURL())
          }
          // document.body.removeChild(img);
          resolve(imageArr)
        })
      })
    })(frames)

    return new CallbackProperty(() => {
      if (frames.length) {
        if (index < frames.length - 1) {
          index += 500 / interval
        } else {
          index = 0
        }
        return frames[Math.floor(index)]
      }
      return url // 因为loadGif是异步的，在解析完成之前先使用原图
    }, false)
  }

  /**
   * @descripttion: 渲染图片带居中文字
   * @param {string} url
   * @param {string} text
   * @param {Color} fontColor
   * @return {*}
   */
  drawTextImagefromImg(url: string, text: string, fontColor: Color) {
    const img = new Image()
    img.src = url
    // 创建画布
    const canvas = document.createElement("canvas")
    img.onload = () => {
      // 图片加载完成，才可处理
      canvas.width = img.width
      canvas.height = img.height
      const ctx = canvas.getContext("2d")
      ctx!.fillStyle = "rgba(255, 255, 255, 0)"
      ctx!.fillRect(0, 0, canvas.width, canvas.height)
      ctx!.drawImage(img, 0, 0)
      ctx!.save()
      ctx!.font = `${text}px Arial`
      ctx!.textBaseline = "middle" // 更改字号后，必须重置对齐方式，否则居中麻烦。设置文本的垂直对齐方式
      ctx!.textAlign = "center"
      // let tw = ctx!.measureText(text).width;
      const ftop = canvas.height / 2 - 5
      const fleft = canvas.width / 2
      // cxt.fillStyle="#ff0000";
      // cxt.fillRect(fleft-tw/2,ftop-fsz/2,tw,fsz);//矩形在画布居中方式
      // cxt.fillStyle="#ffffff";
      ctx!.fillText(text, fleft, ftop) // 文本元素在画布居中方式
      ctx!.strokeStyle = fontColor.toCssColorString()
      ctx!.strokeText(text, fleft, ftop) // 文字边框
    }
    return canvas
  }

  /**
   * @descripttion: 根据序列帧渲染图片
   * @param {string} frames 序列帧
   * @param {number} interval 周期
   * @return {*}
   */
  drawImagefromSequenceFrame(frames: string[], interval = 5000) {
    let index = 0
    const image = new CallbackProperty(() => {
      if (frames.length) {
        if (index < frames.length - 1) {
          index += 500 / interval
        } else {
          index = 0
        }
        return frames[Math.floor(index)]
      } else {
        return frames[0] // 因为loadGif是异步的，在解析完成之前先使用原图
      }
    }, false)
    return image
  }

  /**
   * @descripttion: 创建pop元素
   * @param {string} id 元素id
   * @param {object} style 元素样式
   * @return {*}
   */
  createPopupElement(
    id: string,
    style: { backgroundImage: string, width?: number, height?: number },
    title = "",
    content?: HTMLElement
  ) {
    // 判断是否已存在该id元素
    const htmlElement = document.getElementById(id)
    if (htmlElement || !id) return
    // 创建div
    const popDiv = document.createElement("div")
    const popBefore = document.createElement("div")
    // 标牌id
    popDiv.id = id
    // 标牌样式
    /// flex布局
    popDiv.style.display = "block"
    // popDiv.style.alignItems = 'center';
    // popDiv.style.justifyContent = 'center';
    // popDiv.style.alignContent = 'center';
    /// 自动换行
    popDiv.style.flexWrap = "warp"
    popDiv.style.wordBreak = "normal"
    /// 绝对定位
    popDiv.style.position = "absolute"
    popDiv.style.zIndex = "2"
    popDiv.style.width = `${style.width || 300}px`
    // popDiv.style.minWidth = `50px`;
    popDiv.style.height = `${style.height || 200}px`
    // popDiv.style.minHeight = `30px`;
    popDiv.style.backgroundImage = `url(${style.backgroundImage})`
    popDiv.style.backgroundRepeat = "no-repeat"
    popDiv.style.backgroundSize = "100% 100%"
    // popDiv.innerText = title;
    popDiv.style.textAlign = "center"
    popDiv.style.fontSize = "0.2667rem"
    popDiv.style.color = "white"

    // before元素样式
    /// 布局
    popBefore.style.display = "block"
    popBefore.style.width = "0"
    popBefore.style.borderWidth = "15px 8px 8px 8px"
    popBefore.style.borderStyle = "solid"
    popBefore.style.borderColor
      = "rgba(23, 107, 145, 0.7) transparent transparent transparent"
    popBefore.style.position = "absolute"
    popBefore.style.transform = "translateX(-50%)"
    popBefore.style.left = "50%"
    popBefore.style.top = "100%"

    // 标题
    if (title) {
      const header = document.createElement("p")
      header.innerText = title
      // 向标牌中插入标题
      popDiv.appendChild(header)
    }

    // 标牌内容
    content && popDiv.appendChild(content)
    // 向标牌中插入before
    popDiv.appendChild(popBefore)
    // 向body中添加元素
    document.body.appendChild(popDiv)

    return popDiv
  }

  /**
   * @descripttion: 渲染元素到球上
   * @param {Viewer} viewer 视图
   * @param {HTMLDivElement} elementOrId 元素或者元素ID
   * @param {Cartesian3} position 位置
   * @param {Cartesian2} offset 偏移量
   * @param {number} visualDistance
   * @return {*} 监听索引
   */
  renderElement2Scene(
    viewer: Viewer,
    elementOrId: HTMLDivElement | string,
    position: Cartesian3,
    offset: Cartesian2 = new Cartesian2(-41, 0),
    visualDistance = 1500
  ) {
    // 元素
    const htmlOverlay
      = elementOrId instanceof HTMLDivElement
        ? elementOrId
        : document.getElementById(elementOrId)

    // 判断元素有效
    if (!htmlOverlay || !htmlOverlay.id) {
      return
    }
    // 创建监听方法
    const listener = () => {
      // 获取相机位置
      const canvasPosition
          = viewer.scene.cartesianToCanvasCoordinates(position)
      // 相机与业务点距离
      const distance = Cartesian3.distance(
        viewer.camera.position.clone(),
        position
      )
      // 修改元素位置
      if (defined(canvasPosition) && htmlOverlay) {
        const top = Number(
          (canvasPosition.y - htmlOverlay.offsetHeight + offset.x).toFixed(0)
        )
        const left = Number(
          (canvasPosition.x - htmlOverlay.offsetWidth / 2 + offset.y).toFixed(0)
        )
        htmlOverlay.style.width = `${300}px`
        htmlOverlay.style.height = `${200}px`
        htmlOverlay.style.fontSize = `${14}px`
        htmlOverlay.style.top = `${top}px`
        htmlOverlay.style.left = `${left}px`
        // 超出可视距离隐藏
        htmlOverlay.style.display
          = distance >= visualDistance ? "none" : "block"
      }
    }

    // 添加监听
    this.sceneListener?.add(
      htmlOverlay.id,
      SceneListenerType.preRender,
      listener
    )

    // 返回监听索引
    return htmlOverlay.id
  }

  /**
   * @descripttion: 销毁元素
   * @param {string} id
   * @return {*}
   */
  destroySceneElement(id: string) {
    let result = false
    if (!this.sceneListener?.isExists(id)) return result
    const htmlElement = document.getElementById(id)
    if (htmlElement) {
      // 移除元素
      htmlElement.remove()
      // 删除监听
      this.sceneListener?.removeByCode(id)
      result = true
    }
    return result
  }

  /**
   * @descripttion: 创建扩散圆实体
   * @param {GCS} gcs 地理坐标
   * @param {number} maxA 长半轴
   * @param {number} maxB 短半轴
   * @param {number} interval 扩散周期(ms)
   * @param {HTMLCanvasElement} canvas 画布
   * @return {Entity}
   */
  createDiffuseEllipse(
    gcs: GCS,
    maxA: number,
    maxB: number,
    canvas: HTMLCanvasElement,
    interval = 5000,
    entityOptions?: Entity.ConstructorOptions
  ) {
    let r1 = 0
    let r2 = 0
    const maxR = (maxA + maxB) / 2
    function changeR1() {
      // 这是callback，参数不能内传
      r1 = r1 + 500 / interval
      if (r1 >= maxA) {
        r1 = 0
      }
      return r1
    }
    function changeR2() {
      r2 = r2 + 500 / interval
      if (r2 >= maxB) {
        r2 = 0
      }
      return r2
    }

    return new Entity({
      description: "Diffuse_Circle",
      position: MathUtils.gcsToCartesian3(gcs),
      show: true,
      ellipse: {
        height: 0.01,
        // heightReference: HeightReference.CLAMP_TO_GROUND,
        semiMajorAxis: new CallbackProperty(changeR1, false),
        semiMinorAxis: new CallbackProperty(changeR2, false),
        material: new ImageMaterialProperty({
          image: canvas,
          repeat: new Cartesian2(1.0, 1.0), // 指定图像在每个方向上重复的次数,默认为Cesium.Cartesian2(1.0, 1.0),{Cartesian2}类型
          transparent: true, // 默认为false，当图像具有透明性时设置为true（例如，当png具有透明部分时）
          color: new CallbackProperty(() => {
            const alp = 1 - r1 / maxR
            return Color.WHITE.withAlpha(alp)
            // entity的颜色透明 并不影响材质，并且 entity也会透明
          }, false)
        })
      },
      ...entityOptions
    })
  }

  /**
   * @descripttion: 创建波纹扩散圆实体
   * @param {string} name 名称
   * @param {GCS} gcs 坐标
   * @param {number} maxRadius 最大半径
   * @param {number} quantity 波纹数量
   * @param {number} interval 扩散周期(ms)
   * @param {HTMLCanvasElement} canvas 画布
   * @return {*}
   */
  renderRippleDiffuseCircle(
    name: string,
    gcs: GCS,
    maxRadius: number,
    canvas: HTMLCanvasElement,
    quantity = 3,
    interval = 5000
  ) {
    // 创建波纹
    let index = 0
    const timer = setInterval(() => {
      let r1 = 0
      let r2 = 0
      const step = CesiumMath.lerp(0, maxRadius, 5 / interval)

      const callbackRadius = new CallbackProperty(() => {
        r1 += step
        r1 >= maxRadius && (r1 = 0)
        return CesiumMath.lerp(r1, maxRadius, step)
      }, false)

      const callbackRadius2 = new CallbackProperty(() => {
        r2 += step
        r2 >= maxRadius && (r2 = 0)
        return CesiumMath.lerp(r2, maxRadius, step)
      }, false)

      const entity = new Entity({
        description: "Diffuse_Ripple",
        position: MathUtils.gcsToCartesian3(gcs),
        show: true,
        ellipse: {
          height: 0.01,
          // heightReference: HeightReference.CLAMP_TO_GROUND,
          semiMajorAxis: callbackRadius,
          semiMinorAxis: callbackRadius2,
          material: new ImageMaterialProperty({
            image: canvas,
            repeat: new Cartesian2(1.0, 1.0), // 指定图像在每个方向上重复的次数,默认为Cesium.Cartesian2(1.0, 1.0),{Cartesian2}类型
            transparent: true, // 默认为false，当图像具有透明性时设置为true（例如，当png具有透明部分时）
            color: new CallbackProperty(() => {
              const alp = 1 - (r1 + r2) / 2 / maxRadius
              return Color.WHITE.withAlpha(alp)
              // entity的颜色透明 并不影响材质，并且 entity也会透明
            }, false)
          })
        }
      })

      this.viewer.entities.add(entity)
      index++
      index === 3 && clearTimeout(timer)
    }, interval / quantity)
  }

  /**
   * @descripttion: 批量创建扩散圆实体
   * @param {ICircleWithPosition} arr 数组
   * @param {string} name 数据源名称
   * @param {number} maxA 长半轴
   * @param {number} maxB 短半轴
   * @param {number} canvasRadius 画布半径(pixel)
   * @param {number} interval 扩散周期(ms)
   * @param {boolean} reverse 是否反转
   * @return {*}
   */
  batchCreationDiffuseEllipse(
    arr: ICircleWithPosition[],
    name: string,
    maxA: number,
    maxB: number,
    canvasRadius: number,
    interval = 5000,
    reverse = false
  ) {
    // 变化回调
    let r1 = 0
    let r2 = 0
    const callbackR1 = new CallbackProperty(() => {
      // 这是callback，参数不能内传
      r1 = r1 + 500 / interval
      if (r1 >= maxA) {
        r1 = 0
      }
      return r1
    }, false)
    const callbackR2 = new CallbackProperty(() => {
      r2 = r2 + 500 / interval
      if (r2 >= maxB) {
        r2 = 0
      }
      return r2
    }, false)
    // 创建数据源
    const datasource = new CustomDataSource(name)
    arr.forEach(({ color, blur, lineWidth, gcs }) => {
      const canvas = this.drawCircleImage(
        canvasRadius,
        color,
        blur,
        lineWidth,
        reverse
      )
      const entity = new Entity({
        position: MathUtils.gcsToCartesian3(gcs),
        ellipse: {
          height: 0.01,
          semiMajorAxis: callbackR1,
          semiMinorAxis: callbackR2,
          material: new ImageMaterialProperty({
            image: canvas,
            repeat: new Cartesian2(1, 1),
            transparent: true
          })
        }
      })
      // 添加实体
      datasource.entities.add(entity)
    })
    // 返回数据源
    return datasource
  }

  /**
   * @descripttion: 创建选择框
   * @param {Cartesian3} position
   * @return {*}
   */
  private createSelectedEntity(position: Cartesian3) {
    return new Entity({
      id: "selected",
      position,
      billboard: {
        image: SelectionBox,
        scale: 1
        // scaleByDistance: new NearFarScalar(20000, 1, 5000000, 0.4),
        // distanceDisplayCondition:new Cesium.DistanceDisplayCondition(10.0, 1100000.0),
        // label: {
        //   text: item['enterName'],
        //   font: '20px Helvetica',
        //   outlineColor: new Cesium.Color(0, 1),
        //   outlineWidth: 2,
        //   showBackground: false,
        //   style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        //   backgroundColor: new Cesium.Color(90 / 255, 181 / 255, 1.3),	//背景颜色
        //   fillColor: new Cesium.Color(1, 1, 1, 1),			//填充颜色
        //   horizontalOrigin: Cesium.HorizontalOrigin.LEFT,			//广告牌在文字的方位
        //   pixelOffset: new Cesium.Cartesian2(20.0, -30.0),	//label 向右偏移 billboard的1/2的宽度
        //   distanceDisplayCondition: new Cesium.DistanceDisplayCondition(10.0, 80000.0),
        //   disableDepthTestDistance: 100.0,
      }
      // },
    })
  }

  /**
   * @descripttion: 设置当前选中实体
   * @param {*}
   * @return {*}
   */
  //   setSelectedEntity(position: Cartesian3) {
  //     // 移除上一个选择框
  //     this.viewer.entities.removeById("selected");
  //     // 创建选择框
  //     const entity = this.createSelectedEntity(position);

  //     entity && this.viewer.entities.add(entity);
  //   }

  /**
   * @descripttion: 移除选则框
   * @param {*}
   * @return {*}
   */
  //   removeSelectedEntity() {
  //     this.viewer.entities.removeById("selected");
  //   }

  /**
   * 开启实体选择框监听
   * @descripttion:
   * @param {*} guid
   * @param {Function} callback
   * @return {*}
   */
  entitySelectedListener(callback: (res: Entity | undefined) => void) {
    return this.viewer.selectedEntityChanged.addEventListener(callback)
  }

  /*
   * @descripttion: 渲染鼠标跟随框
   * @param {string} id
   * @param {string} tip
   * @param {CSSStyleDeclaration} style
   * @return {*}
   */
  renderMouseTip(
    id: string = createGuid(),
    tip = "请在场景中，选择发生位置",
    style?: CSSStyleDeclaration
  ): string {
    const toolTip = document.createElement("div")
    const popBefore = document.createElement("div")
    const container
        = this.container instanceof HTMLDivElement
          ? this.container
          : document.getElementById(this.container as string)
    // 设置容器样式
    toolTip.id = id
    toolTip.style.position = "absolute"
    toolTip.style.zIndex = "2"
    toolTip.innerHTML = tip
    toolTip.style.color = style?.color || "#FFF"
    toolTip.style.margin = style?.margin || "5px"
    toolTip.style.backgroundColor = style?.backgroundColor || "#636770c7"
    toolTip.style.border = style?.border || "1px solid #16abd8b5"
    toolTip.style.borderRadius = style?.borderRadius || "5px"
    // 伪元素标签
    // before元素样式
    popBefore.style.display = "block"
    popBefore.style.width = "0"
    popBefore.style.borderWidth = "5px 11px 5px 5px"
    popBefore.style.borderStyle = "solid"
    popBefore.style.borderColor
      = "transparent #16abd8b5 transparent transparent"
    popBefore.style.position = "absolute"
    popBefore.style.transform = "translateY(-50%)"
    popBefore.style.left = "-15px"
    popBefore.style.top = "50%"
    // 添加提示框到容器上
    container?.appendChild(toolTip)
    // 添加伪元素到信息框
    toolTip.appendChild(popBefore)
    // 鼠标移动监听
    this.mouseHandlerManager?.addMouseMoveHandler(id, ({ screenPosition }) => {
      // 修改元素位置
      if (screenPosition) {
        toolTip.style.top = `${screenPosition.y}px`
        toolTip.style.left = `${screenPosition.x + 15}px`
      }
    })
    return id
  }

  /**
   * @descripttion: 移除鼠标跟随框
   * @param {string} id
   * @return {*}
   */
  removeMouseTipByID(id: string) {
    this.mouseHandlerManager?.destroyHandlerByKey(id)
    const htmlElement = document.getElementById(id)
    if (htmlElement) {
      // 移除元素
      htmlElement.remove()
    }
  }

  /**
   *创建飞行轨迹线
   * @param {*} data 点数据
   * @returns {null} null
   */
  // createFlyLines(data) {
  //     const center = data.center;
  //     const cities = data.points;
  //     const startPoint = Cesium.Cartesian3.fromDegrees(
  //         center.lon,
  //         center.lat,
  //         0,
  //     );
  //     //创建实体数据集
  //     let dataSource = new Cesium.CustomDataSource('数据中心');
  //     //中心点
  //     dataSource.entities.add(new Cesium.Entity({
  //         position: startPoint,
  //         point: {
  //             pixelSize: center.size,
  //             color: center.color,
  //         },
  //     }));
  //     // this.viewer.entities.add({
  //     //     position: startPoint,
  //     //     point: {
  //     //         pixelSize: center.size,
  //     //         color: center.color
  //     //     }
  //     // });
  //     //大批量操作时，临时禁用事件可以提高性能
  //     // this.viewer.entities.suspendEvents();
  //     //散点
  //     cities.forEach(city => {
  //         let material = new PolylineTrailMaterialProperty({
  //             color: new Cesium.Color.fromCssColorString('rgba(84,112,155,.5)'),
  //             duration: 3000,
  //             trailImage: './assets/flyPolyline/colors2.png',
  //         });
  //         const endPoint = Cesium.Cartesian3.fromDegrees(city.lon, city.lat);
  //         //终点
  //         dataSource.entities.add(new Cesium.Entity({
  //             position: endPoint,
  //             point: {
  //                 pixelSize: city.size - 10,
  //                 color: city.color,
  //             },
  //         }));
  //         //抛物线
  //         dataSource.entities.add(new Cesium.Entity({
  //             polyline: {
  //                 positions: this.generateCurve(endPoint, startPoint),
  //                 width: Math.round(Math.random() * 4),
  //                 material: material,
  //             },
  //         }));
  //         // this.viewer.entities.add({
  //         //     position: endPoint,
  //         //     point: {
  //         //         pixelSize: city.size - 10,
  //         //         color: city.color
  //         //     }
  //         // });
  //         // this.viewer.entities.add({
  //         //     polyline: {
  //         //         positions: this.generateCurve(startPoint, endPoint),
  //         //         width: 2,
  //         //         material: material
  //         //     }
  //         // });
  //     });
  //     this.viewer.dataSources.add(dataSource);
  //     this.layerRecordsManager.add('code', 0, dataSource);
  //     // this.viewer.entities.resumeEvents();
  //     this.viewer.flyTo(this.viewer.entities);
  // }

  /**
   * 渲染流动曲线
   * @param {object} startPoint 起点
   * @param {object} endPoint 终点
   * @returns {Array} 曲线
   */
  // generateCurve(startPoint, endPoint) {
  //     let addPointCartesian = new Cesium.Cartesian3();
  //     Cesium.Cartesian3.add(startPoint, endPoint, addPointCartesian);
  //     let midPointCartesian = new Cesium.Cartesian3();
  //     Cesium.Cartesian3.divideByScalar(addPointCartesian, 2, midPointCartesian);
  //     let midPointCartographic = Cesium.Cartographic.fromCartesian(
  //         midPointCartesian,
  //     );
  //     midPointCartographic.height =
  //         Cesium.Cartesian3.distance(startPoint, endPoint) / 5;
  //     let midPoint = new Cesium.Cartesian3();
  //     Cesium.Ellipsoid.WGS84.cartographicToCartesian(
  //         midPointCartographic,
  //         midPoint,
  //     );
  //     let spline = new Cesium.CatmullRomSpline({
  //         times: [0.5, 1.0],
  //         points: [startPoint, midPoint, endPoint],
  //     });
  //     let curvePoints = [];
  //     for (let i = 0, len = 200; i < len; i++) {
  //         curvePoints.push(spline.evaluate(i / len));
  //     }
  //     return curvePoints;
  // }
  // ------------------------------------------------------------------------------
}
